/*
 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing.plaf.nimbus;

import javax.swing.border.Border;
import javax.swing.JComponent;
import java.awt.Insets;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.RenderingHints;
import java.awt.Dimension;
import java.awt.image.BufferedImage;

/**
 * LoweredBorder - A recessed rounded inner shadowed border. Used as the
 * standard Nimbus TitledBorder. This class is both a painter and a swing
 * border.
 *
 * @author Jasper Potts
 */
class LoweredBorder extends AbstractRegionPainter implements Border {

  private static final int IMG_SIZE = 30;
  private static final int RADIUS = 13;
  private static final Insets INSETS = new Insets(10, 10, 10, 10);
  private static final PaintContext PAINT_CONTEXT = new PaintContext(INSETS,
      new Dimension(IMG_SIZE, IMG_SIZE), false,
      PaintContext.CacheMode.NINE_SQUARE_SCALE,
      Integer.MAX_VALUE, Integer.MAX_VALUE);

  // =========================================================================
  // Painter Methods

  @Override
  protected Object[] getExtendedCacheKeys(JComponent c) {
    return (c != null)
        ? new Object[]{c.getBackground()}
        : null;
  }

  /**
   * Actually performs the painting operation. Subclasses must implement this
   * method. The graphics object passed may represent the actual surface being
   * rendered to, or it may be an intermediate buffer. It has also been
   * pre-translated. Simply render the component as if it were located at 0, 0
   * and had a width of <code>width</code> and a height of
   * <code>height</code>. For performance reasons, you may want to read the
   * clip from the Graphics2D object and only render within that space.
   *
   * @param g The Graphics2D surface to paint to
   * @param c The JComponent related to the drawing event. For example, if the region being rendered
   * is Button, then <code>c</code> will be a JButton. If the region being drawn is ScrollBarSlider,
   * then the component will be JScrollBar. This value may be null.
   * @param width The width of the region to paint. Note that in the case of painting the
   * foreground, this value may differ from c.getWidth().
   * @param height The height of the region to paint. Note that in the case of painting the
   * foreground, this value may differ from c.getHeight().
   */
  protected void doPaint(Graphics2D g, JComponent c, int width, int height,
      Object[] extendedCacheKeys) {
    Color color = (c == null) ? Color.BLACK : c.getBackground();
    BufferedImage img1 = new BufferedImage(IMG_SIZE, IMG_SIZE,
        BufferedImage.TYPE_INT_ARGB);
    BufferedImage img2 = new BufferedImage(IMG_SIZE, IMG_SIZE,
        BufferedImage.TYPE_INT_ARGB);
    // draw shadow shape
    Graphics2D g2 = (Graphics2D) img1.getGraphics();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(color);
    g2.fillRoundRect(2, 0, 26, 26, RADIUS, RADIUS);
    g2.dispose();
    // draw shadow
    InnerShadowEffect effect = new InnerShadowEffect();
    effect.setDistance(1);
    effect.setSize(3);
    effect.setColor(getLighter(color, 2.1f));
    effect.setAngle(90);
    effect.applyEffect(img1, img2, IMG_SIZE, IMG_SIZE);
    // draw outline to img2
    g2 = (Graphics2D) img2.getGraphics();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setClip(0, 28, IMG_SIZE, 1);
    g2.setColor(getLighter(color, 0.90f));
    g2.drawRoundRect(2, 1, 25, 25, RADIUS, RADIUS);
    g2.dispose();
    // draw final image
    if (width != IMG_SIZE || height != IMG_SIZE) {
      ImageScalingHelper.paint(g, 0, 0, width, height, img2, INSETS, INSETS,
          ImageScalingHelper.PaintType.PAINT9_STRETCH,
          ImageScalingHelper.PAINT_ALL);
    } else {
      g.drawImage(img2, 0, 0, c);
    }
    img1 = null;
    img2 = null;
  }

  /**
   * <p>Gets the PaintContext for this painting operation. This method is
   * called on every paint, and so should be fast and produce no garbage. The
   * PaintContext contains information such as cache hints. It also contains
   * data necessary for decoding points at runtime, such as the stretching
   * insets, the canvas size at which the encoded points were defined, and
   * whether the stretching insets are inverted.</p>
   * <p/>
   * <p> This method allows for subclasses to package the painting of
   * different states with possibly different canvas sizes, etc, into one
   * AbstractRegionPainter implementation.</p>
   *
   * @return a PaintContext associated with this paint operation.
   */
  protected PaintContext getPaintContext() {
    return PAINT_CONTEXT;
  }

  // =========================================================================
  // Border Methods

  /**
   * Returns the insets of the border.
   *
   * @param c the component for which this border insets value applies
   */
  public Insets getBorderInsets(Component c) {
    return (Insets) INSETS.clone();
  }

  /**
   * Returns whether or not the border is opaque.  If the border is opaque, it
   * is responsible for filling in it's own background when painting.
   */
  public boolean isBorderOpaque() {
    return false;
  }

  /**
   * Paints the border for the specified component with the specified position
   * and size.
   *
   * @param c the component for which this border is being painted
   * @param g the paint graphics
   * @param x the x position of the painted border
   * @param y the y position of the painted border
   * @param width the width of the painted border
   * @param height the height of the painted border
   */
  public void paintBorder(Component c, Graphics g, int x, int y, int width,
      int height) {
    JComponent comp = (c instanceof JComponent) ? (JComponent) c : null;
    if (g instanceof Graphics2D) {
      Graphics2D g2 = (Graphics2D) g;
      g2.translate(x, y);
      paint(g2, comp, width, height);
      g2.translate(-x, -y);
    } else {
      BufferedImage img = new BufferedImage(IMG_SIZE, IMG_SIZE,
          BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = (Graphics2D) img.getGraphics();
      paint(g2, comp, width, height);
      g2.dispose();
      ImageScalingHelper.paint(g, x, y, width, height, img, INSETS, INSETS,
          ImageScalingHelper.PaintType.PAINT9_STRETCH,
          ImageScalingHelper.PAINT_ALL);
    }
  }

  private Color getLighter(Color c, float factor) {
    return new Color(Math.min((int) (c.getRed() / factor), 255),
        Math.min((int) (c.getGreen() / factor), 255),
        Math.min((int) (c.getBlue() / factor), 255));
  }
}

